package fr.pud.client.view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Stroke;
import java.util.ArrayList;

import fr.pud.client.i18n.LanguagesConstants;
import fr.pud.client.model.PUDStation;
import fr.pud.client.model.Route;
import fr.pud.client.model.Routes;
import fr.pud.client.model.Vehicle;
import fr.pud.client.view.mapstation.MapStation;
import fr.pud.utils.Tools;

/**
 * Apr 8, 2012 - Projet_Ose.
 * @author Simon Devineau, Adrien Hierry, Leopold Houdin, Pierre Reliquet
 *         Ecole des Mines de Nantes
 *         MapRoute.java
 *         A MapRoute is the representation of a route.
 *         For each route, a different color is used to draw the line between
 *         all the stations of this one.
 */
public class MapRoute {
    /**
     * An int which represents the fact that a point is situated a the north
     * west of a second one.
     */
    private static final int      NORTH_WEST      = 1;
    /**
     * An int which represents the fact that a point is situated a the north
     * east of a second one.
     */
    private static final int      NORTH_EAST      = 2;
    /**
     * An int which represents the fact that a point is situated a the south
     * east of a second one.
     */
    private static final int      SOUTH_EAST      = 3;
    /**
     * An int which represents the fact that a point is situated a the south
     * west of a second one.
     */
    private static final int      SOUTH_WEST      = 4;
    /**
     * The thickness of the line which represents the road.
     */
    public static final int       THICKNESS_LINES = 3;
    /**
     * A list of coordinates which represents all the points oh the route
     */
    private ArrayList<MapStation> route           = new ArrayList<MapStation>();
    /**
     * A list of values which represents all the slopes between each points of
     * the route
     */
    private ArrayList<Double>     slopes          = new ArrayList<Double>();
    /**
     * The color of the line which represents the route
     */
    private Color                 color;
    /**
     * Inform about the visibility of a MapRoute
     */
    private boolean               isVisible       = true;
    /**
     * The vehicle used for the route
     */
    private Vehicle               vehicle;

    /**
     * The default constructor
     */
    public MapRoute() {
        this.color = Tools.COLORS[Routes.getInstance().getRoutes().size()
                % Tools.COLORS.length];
    }

    /**
     * The default constructor
     */
    public MapRoute(Color c) {
        this.color = c;
        this.vehicle = new Vehicle();
    }

    /**
     * @param route
     *            , the route to draw
     * @param c
     *            , the color chosen for draw this toute.
     */
    public MapRoute(Route route, Vehicle vehicle, Color c) {
        ArrayList<PUDStation> pudStationsList = (ArrayList<PUDStation>) route
                .getStations();
        for (int i = 0; i < pudStationsList.size(); i++) {
            if (i == 0) {
                this.route.add(new MapStation(pudStationsList.get(i), "" + i,
                        route.getVehicle(), MapStation.GREEN_POINTER));
            }
            else if (i == pudStationsList.size() - 1) {
                this.route.add(new MapStation(pudStationsList.get(i), "" + i,
                        route.getVehicle(), MapStation.BLUE_POINTER));
            }
            else {
                this.route.add(new MapStation(pudStationsList.get(i), "" + i,
                        route.getVehicle(), MapStation.RED_POINTER));
            }
        }
        this.calculateSlopes();
        this.vehicle = vehicle;
        this.color = c;
    }

    /**
     * add a point to the Route
     * @param myCoordinates
     *            , the coordinate we want to add
     */
    public void addMapAddress(PUDStation station, Vehicle vehicle) {
        this.getRoute().add(
                new MapStation(station, vehicle, MapStation.RED_POINTER));
    }

    /**
     * Calculate the slope of the straight line which link two stations.
     * The list of slopes will be useful to draw line even if a station is
     * outside the map.
     */
    public void calculateSlopes() {
        for (int i = 0; i < this.route.size() - 1; i++) {
            this.slopes.add((this.route.get(i + 1).getLat() - this.route.get(i)
                    .getLat())
                    / (this.route.get(i + 1).getLon() - this.route.get(i)
                            .getLon()));
        }
    }

    /**
     * @param p1
     *            , the first point to compare.
     * @param p2
     *            , the second point to compare.
     * @return an int which represents the position of the p2 according to the
     *         position of p1.
     */
    private int comparisonPoints(MapStation p1, MapStation p2) {
        if (p2.getLat() >= p1.getLat()) {
            if (p2.getLon() >= p1.getLon()) {
                return NORTH_EAST;
            }
            else {
                return NORTH_WEST;
            }
        }
        else {
            if (p2.getLon() >= p1.getLon()) {
                return SOUTH_EAST;
            }
            else {
                return SOUTH_WEST;
            }
        }
    }

    /**
     * @return the color
     */
    public Color getColor() {
        return this.color;
    }

    /**
     * @param i
     *            refers to the coordinate we want
     * @return the coordinate we want
     */
    public MapStation getMapStation(int i) {
        return this.getRoute().get(i);
    }

    /**
     * @return the list of the coordinates
     */
    public ArrayList<MapStation> getRoute() {
        return this.route;
    }

    /**
     * @return the number of point of the Route
     */
    public int getSize() {
        return this.getRoute().size();
    }

    /**
     * @return the list of slopes
     */
    public ArrayList<Double> getSlopes() {
        return this.slopes;
    }

    /**
     * @return the vehicle
     */
    public Vehicle getVehicle() {
        return this.vehicle;
    }

    /**
     * If a station is outside the map, the line which represents the route will
     * be also print.
     * The point situated on the straight line and which is exactly on a side of
     * the map has to be calculated to draw this line.
     * @param p
     *            , the point of the route which is printed on the map.
     * @param slope
     *            , the slope of the straight line which represents the route.
     * @param location
     *            , the direction of the route.
     * @return, the point which is on the route and on a side of the map.
     */
    private Point intersectionPoint(Point p, double slope, int location) {
        int xMax = JMapViewerExtended.getJMapViewer().getWidth();
        int yMax = JMapViewerExtended.getJMapViewer().getHeight();
        int leftIntersect = (int) (p.y - slope * p.x);
        int upIntersect = (int) (-leftIntersect / slope);
        int rightIntersect = (int) (slope * xMax + leftIntersect);
        int downIntersect = (int) ((yMax - leftIntersect) / slope);
        switch (location) {
            case NORTH_WEST:
                if (leftIntersect < 0) {
                    return new Point(upIntersect, 0);
                }
                else {
                    return new Point(0, leftIntersect);
                }
            case NORTH_EAST:
                if (rightIntersect < 0) {
                    return new Point(upIntersect, 0);
                }
                else {
                    return new Point(xMax, rightIntersect);
                }
            case SOUTH_WEST:
                if (leftIntersect > yMax) {
                    return new Point(downIntersect, yMax);
                }
                else {
                    return new Point(0, leftIntersect);
                }
            case SOUTH_EAST:
                if (rightIntersect > yMax) {
                    return new Point(downIntersect, yMax);
                }
                else {
                    return new Point(xMax, rightIntersect);
                }
            default:
                return null;
        }
    }

    /**
     * @return the isVisible
     */
    public boolean isVisible() {
        return this.isVisible;
    }

    /**
     * draw the Route on the map
     * @param g
     *            , the graphics where the Route is painted
     * @param RoutePoint
     *            , the list of MapPoint of the Route
     */
    public void paint(Graphics g, ArrayList<Point> RoutePoint) {
        if (this.isVisible && JMapViewerExtended.getJMapViewer().getZoom() > 7) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(this.color);
            Stroke s = g2d.getStroke();
            g2d.setStroke(new BasicStroke(THICKNESS_LINES));
            for (int indexCoordinates = 0; indexCoordinates < RoutePoint.size() - 1; indexCoordinates++) {
                Point p1 = RoutePoint.get(indexCoordinates);
                Point p2 = RoutePoint.get(indexCoordinates + 1);
                if (p1 != null || p2 != null) {
                    if (p1 != null && p2 != null) {
                        g2d.drawLine(p1.x, p1.y, p2.x, p2.y);
                    }
                    else {
                        Point p = p1 == null ? p2 : p1;
                        double slope = this.slopes.get(indexCoordinates);
                        Point limitPoint;
                        if (p1 == null) {
                            int comparison = this.comparisonPoints(
                                    this.route.get(indexCoordinates + 1),
                                    this.route.get(indexCoordinates));
                            limitPoint = this.intersectionPoint(p, -slope,
                                    comparison);
                        }
                        else {
                            int comparison = this.comparisonPoints(
                                    this.route.get(indexCoordinates),
                                    this.route.get(indexCoordinates + 1));
                            limitPoint = this.intersectionPoint(p, -slope,
                                    comparison);
                        }
                        g2d.drawLine(p.x, p.y, limitPoint.x, limitPoint.y);
                    }
                }
            }
            g2d.setStroke(s);
        }
    }

    /**
     * @param aColor
     *            the color to set
     */
    public void setColor(Color aColor) {
        this.color = aColor;
        JMapViewerExtended.getJMapViewer().repaint();
    }

    /**
     * @param vehicle
     *            the vehicle to set
     */
    public void setVehicle(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    /**
     * @param isVisible
     *            the isVisible to set
     */
    public void setVisible(boolean isVisible) {
        this.isVisible = isVisible;
    }

    @Override
    public String toString() {
        return LanguagesConstants.LANGUAGE.getString(LanguagesConstants.ROUTES)
                + " : " + this.vehicle.toString() + "\n" + this.color;
    }
}
